import CodoError from "../CodoError";
import WeWorkBaseService from "./WeWorkBaseService";
import { WeWork } from "./type";

/** 自建应用方式，缓存access token的key */
const getAppTokenRedisKey = (corpId: string) => `codo:wework:auth:${corpId}`;

/** 服务商的缓存token key */
const getProviderTokenRedisKey = (corpId: string) => `codo:wework:auth:provider:${corpId}`;
/** 第三方应用的缓存token key */
const getSuiteTokenRedisKey = (appId: string) => `codo:wework:auth:suite:${appId}`;

/** 第三方应用下，获取单个企业的缓存token key */
const getThirdCorpTokenRedisKey = (corpid: string, permanentCode: string) =>
	`codo:wework:auth:suiteCorp:${corpid}:${permanentCode}`;

export default class Token extends WeWorkBaseService {
	/** 更新企微access_token
	 * - 一般不建议直接调用（可能导致之前有效的token失效，且频率太高企微会拦截），可使用getToken获取
	 */
	async refreshAccessToken() {
		const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${this.config.appId}&corpsecret=${this.config.secret}`;

		const response = await this.curl<WeWork.IGetAccessTokenResponse>(url, {
			method: "GET",
			dataType: "json",
			timeout: [30000, 30000],
		});
		const res = response.data;
		if (res.errcode === 0) {
			const key = getAppTokenRedisKey(this.config.appId);
			await this.app.redis.set(key, JSON.stringify(res), "EX", res.expires_in - 1);
			return res;
		}
		this.app.logger.error(
			"[wework] 更新accessToken失败 %s %s appId %s",
			res.errcode,
			res.errmsg,
			this.config.appId
		);
		throw new CodoError(res.errmsg);
	}

	/** 获取accessToken
	 * - 有redis缓存的，可直接调用
	 */
	async getToken() {
		const key = getAppTokenRedisKey(this.config.appId);
		const token = await this.app.redis.get(key);
		if (token) {
			return JSON.parse(token) as WeWork.IGetAccessTokenResponse;
		}
		return await this.refreshAccessToken();
	}

	/**
	 * - 获取服务商的accessToken
	 * - 第三方应用使用
	 * - 不应频繁调用
	 * - 注意此处需要传入的config是服务商的corpid等信息
	 * */
	async refreshProviderToken() {
		const url = `https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token`;

		const response = await this.curl<WeWork.IGetProviderAccessTokenResponse>(url, {
			method: "POST",
			dataType: "json",
			data: {
				corpid: this.config.appId,
				provider_secret: this.config.secret,
			},
			timeout: [30000, 30000],
		});
		const res = response.data;
		if (res.errcode && res.errcode !== 0) {
			this.app.logger.error(
				"[wework] 更新provider accessToken失败 %s %s appId %s",
				res.errcode,
				res.errmsg,
				this.config.appId
			);
			throw new CodoError(res?.errmsg || "unknown");
		}
		const key = getProviderTokenRedisKey(this.config.appId);
		await this.app.redis.set(key, JSON.stringify(res), "EX", res.expires_in - 1);
		return res;
	}

	/** 获取服务商的accessToken
	 * - 有redis缓存的，可直接调用
	 */
	async getProviderToken() {
		const key = getProviderTokenRedisKey(this.config.appId);
		const token = await this.app.redis.get(key);
		if (token) {
			return JSON.parse(token) as WeWork.IGetProviderAccessTokenResponse;
		}
		return await this.refreshProviderToken();
	}

	/**
	 * 刷新第三方应用凭证
	 * - https://developer.work.weixin.qq.com/document/path/90600
	 * @param {string} suite_ticket ticket是企微每10min向我方回调过来的ticket
	 */
	async refreshSuiteToken(suite_ticket: string) {
		const url = `https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token`;

		const suiteId = this.config.appId;
		const data = {
			suite_id: suiteId,
			suite_secret: this.config.secret,
			suite_ticket,
		};

		const res = await this.curl<WeWork.IGetSuiteAccessTokenResponse>(url, {
			method: "POST",
			dataType: "json",
			contentType: "json",
			data,
			timeout: [30000, 30000],
		});

		if (res.data.errcode && res.data.errcode != 0) {
			this.app.logger.error(
				"[wework] 更新suite accessToken失败 %s %s appId %s",
				res.data.errcode,
				res.data.errmsg,
				this.config.appId
			);
			throw new CodoError(res.data.errmsg || "unknown");
		}
		const key = getSuiteTokenRedisKey(suiteId);
		await this.app.redis.set(key, JSON.stringify(res), "EX", res.data.expires_in - 1);

		return res.data;
	}

	/**
	 * 获取第三方应用凭证
	 */
	async getSuiteToken(suite_ticket: string) {
		const cache = await this.app.redis.get(getSuiteTokenRedisKey(this.config.appId));
		if (cache) return JSON.parse(cache) as WeWork.IGetSuiteAccessTokenResponse;
		return await this.refreshSuiteToken(suite_ticket);
	}

	/**
	 * 刷新第三方应用下，授权企业的凭证（与自建应用获取的企业凭证一样，拿到它后，就可以调用普通业务接口了）
	 * - https://developer.work.weixin.qq.com/document/path/90605
	 */
	async refreshSuiteCorpToken(
		auth_corpid: string,
		permanent_code: string,
		suiteAccessToken: string
	) {
		const url = `https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token?suite_access_token=${suiteAccessToken}`;

		const data = {
			auth_corpid,
			permanent_code,
		};

		const res = await this.curl<WeWork.IGetAccessTokenResponse>(url, {
			method: "POST",
			dataType: "json",
			contentType: "json",
			data,
			timeout: [30000, 30000],
		});

		if (res.data.errcode && res.data.errcode != 0) {
			this.app.logger.error(
				"[wework] 更新suite corp accessToken失败 %s %s appId %s",
				res.data.errcode,
				res.data.errmsg,
				this.config.appId
			);
			throw new CodoError(res.data.errmsg || "unknown");
		}
		const key = getThirdCorpTokenRedisKey(auth_corpid, permanent_code);
		await this.app.redis.set(key, JSON.stringify(res), "EX", res.data.expires_in - 1);

		return res.data;
	}

	/**
	 * 获取第三方应用凭证下，客户企业的凭证access token
	 * 拿到后，即可跟自建应用一样调用业务api
	 */
	async getSuiteCorpToken(
		auth_corpid: string,
		permanent_code: string,
		suiteAccessToken: string
	) {
		const key = getThirdCorpTokenRedisKey(auth_corpid, permanent_code);
		const cache = await this.app.redis.get(key);
		if (cache) return JSON.parse(cache) as WeWork.IGetAccessTokenResponse;
		return await this.refreshSuiteCorpToken(auth_corpid, permanent_code, suiteAccessToken);
	}
}
